/*
 *  Copyright (c) M2TK Project. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package m2tk.assistant.app.ui.view;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import m2tk.assistant.app.ui.component.DensityChartBoard;
import m2tk.assistant.app.ui.event.ShowStreamDensityRequest;
import m2tk.assistant.app.ui.model.DensityStatsTableModel;
import m2tk.assistant.app.ui.task.AsyncQueryTask;
import m2tk.assistant.app.ui.util.ComponentUtil;
import m2tk.assistant.common.InfoView;
import m2tk.assistant.common.M2TKDatabase;
import m2tk.assistant.common.StreamObserver;
import m2tk.assistant.common.domain.ElementaryStream;
import m2tk.assistant.common.domain.StreamDensityBulk;
import m2tk.assistant.common.domain.StreamDensityStats;
import m2tk.assistant.common.domain.StreamSource;
import m2tk.assistant.common.event.RefreshInfoViewRequest;
import m2tk.assistant.common.event.ShowInfoViewRequest;
import m2tk.assistant.common.ui.RoundPanel;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.application.Application;
import org.kordamp.ikonli.fluentui.FluentUiRegularMZ;
import org.kordamp.ikonli.swing.FontIcon;
import org.pf4j.Extension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumnModel;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static m2tk.assistant.common.ui.UIConstants.*;

@Extension(ordinal = 4)
public class DensityInfoView extends RoundPanel implements InfoView, StreamObserver
{
    private static final Logger log = LoggerFactory.getLogger(DensityInfoView.class);

    private DensityStatsTableModel statsTableModel;
    private DensityChartBoard chartBoard;
    private JTable table;
    private JSplitPane splitPane;

    private Application application;
    private M2TKDatabase database;
    private EventBus bus;

    private final String viewTitle = "密度分析";
    private final Icon viewIcon = FontIcon.of(FluentUiRegularMZ.PULSE_24, ICON_SIZE_L, LIGHT_BLUE);
    private volatile long lastTimestamp;
    private static final long MIN_QUERY_INTERVAL_MILLIS = 500;

    private static class DensityContext
    {
        private List<StreamDensityStats> stats;
        private int bitrate;
    }

    public DensityInfoView()
    {
        initUI();
    }

    private void initUI()
    {
        statsTableModel = new DensityStatsTableModel();
        table = createTable();
        table.setModel(statsTableModel);
        table.getTableHeader().setReorderingAllowed(false);
        table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setRowSelectionAllowed(true);
        table.setColumnSelectionAllowed(false);
        table.getSelectionModel().addListSelectionListener(e -> {
            if (e.getValueIsAdjusting())
                return;

            int selectedRow = table.getSelectedRow();
            if (selectedRow == -1)
            {
                hideChartBoard();
            } else
            {
                StreamDensityStats stats = statsTableModel.getStatAtRow(selectedRow);
                queryDensityBulks(stats.getPid());
            }
        });

        DefaultTableCellRenderer leadingRenderer = new DefaultTableCellRenderer();
        leadingRenderer.setHorizontalAlignment(SwingConstants.LEADING);

        TableColumnModel columnModel = table.getColumnModel();
        ComponentUtil.configTableColumn(columnModel, 0, leadingRenderer, 60, false);   // 序号
        ComponentUtil.configTableColumn(columnModel, 1, leadingRenderer, 150, false);  // PID
        ComponentUtil.configTableColumn(columnModel, 2, leadingRenderer, 200, false);  // 间隔总数
        ComponentUtil.configTableColumn(columnModel, 3, leadingRenderer, 200, false);  // 平均间隔
        ComponentUtil.configTableColumn(columnModel, 4, leadingRenderer, 200, false);  // 最小间隔
        ComponentUtil.configTableColumn(columnModel, 5, leadingRenderer, 200, false);  // 最大间隔

        chartBoard = new DensityChartBoard();
        splitPane = createVerticalSplitPane(createScrollPane(table), chartBoard);

        setLayout(new MigLayout("insets 10", "[][grow][]", "[25!][pref!][][grow]"));
        add(new JLabel(viewTitle,
                       FontIcon.of(FluentUiRegularMZ.PULSE_24, ICON_SIZE_S, LIGHT_BLUE),
                       SwingConstants.TRAILING),
            "left, wrap");
        add(separator(), "span 3, grow, wrap");
        add(splitPane, "span 3 2, grow");

        hideChartBoard();

        addComponentListener(new ComponentAdapter()
        {
            @Override
            public void componentShown(ComponentEvent e)
            {
                if (database != null)
                    queryDensityStats();
            }
        });
    }

    @Override
    public void setupApplication(Application application)
    {
        this.application = application;
    }

    @Override
    public void setupDataSource(EventBus bus, M2TKDatabase database)
    {
        this.bus = bus;
        this.database = database;

        bus.register(this);
    }

    @Override
    public JComponent getViewComponent()
    {
        return this;
    }

    @Override
    public String getViewTitle()
    {
        return viewTitle;
    }

    @Override
    public Icon getViewIcon()
    {
        return viewIcon;
    }

    @Override
    public List<JMenuItem> getContextMenuItem(ElementaryStream stream)
    {
        JMenuItem item = new JMenuItem("查看流密度");
        item.addActionListener(e -> {
            bus.post(new ShowInfoViewRequest(this));
            bus.post(new ShowStreamDensityRequest(stream.getStreamPid()));
        });
        return List.of(item);
    }

    @Subscribe
    public void onRefreshInfoViewEvent(RefreshInfoViewRequest event)
    {
        long t1 = System.currentTimeMillis();
        if (t1 - lastTimestamp >= MIN_QUERY_INTERVAL_MILLIS && isShowing())
        {
            queryDensityStats();
            lastTimestamp = System.currentTimeMillis();
        }
    }

    @Subscribe
    public void onShowStreamDensityRequest(ShowStreamDensityRequest request)
    {
        queryDensityStatsAndSelect(request.stream());
    }

    private void queryDensityStats()
    {
        Supplier<DensityContext> query = () -> {
            DensityContext context = new DensityContext();
            try
            {
                context.stats = database.listStreamDensityStats();
                context.bitrate = Optional.ofNullable(database.getCurrentStreamSource())
                                          .map(StreamSource::getBitrate)
                                          .orElse(0);
            } catch (Exception ex)
            {
                log.warn("数据库查询异常：{}", ex.getMessage(), ex);
            }

            return context;
        };
        Consumer<DensityContext> consumer = context -> statsTableModel.update(context.bitrate, context.stats);

        AsyncQueryTask<DensityContext> task = new AsyncQueryTask<>(application, query, consumer);
        task.execute();
    }

    private void queryDensityStatsAndSelect(int pid)
    {
        Supplier<DensityContext> query = () ->
        {
            DensityContext context = new DensityContext();
            context.stats = database.listStreamDensityStats();
            context.bitrate = Optional.ofNullable(database.getCurrentStreamSource())
                                      .map(StreamSource::getBitrate)
                                      .orElse(0);
            return context;
        };
        Consumer<DensityContext> consumer = context ->
        {
            statsTableModel.update(context.bitrate, context.stats);

            int row = statsTableModel.getStatRow(pid);
            table.setRowSelectionInterval(row, row);
        };

        AsyncQueryTask<DensityContext> task = new AsyncQueryTask<>(application, query, consumer);
        task.execute();
    }

    private void queryDensityBulks(int pid)
    {
        Supplier<List<StreamDensityBulk>> query = () -> database.getRecentStreamDensityBulks(pid, 2);
        Consumer<List<StreamDensityBulk>> consumer = checks ->
        {
            chartBoard.update(checks);
            showChartBoard();
        };

        AsyncQueryTask<List<StreamDensityBulk>> task = new AsyncQueryTask<>(application, query, consumer);
        task.execute();
    }

    private void hideChartBoard()
    {
        chartBoard.setVisible(false);
        splitPane.setDividerLocation(1.0);
    }

    private void showChartBoard()
    {
        chartBoard.setVisible(true);
        splitPane.setDividerLocation(0.3);
    }
}
